/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;

/**
 * This scene node type is used to draw primitives. The primitives
 * are triangles which can be colored and textured in the same way
 * as in OpenGL and DirectX. The primitives are defined by 
 * attributes. Each attribute is an array of numbers that defines
 * a certain property such as position, color or texture coordinate
 * of the primitives to be drawn. Textures can also be defined.
 * The triangles as defined by an index array (indices).
 */
public class ScenePrimitives extends SceneLeaf {
	/**
	 * This class contains attribute information. The size of the attribute
	 * is not the size of the array, but the number of elements that are used
	 * for one vertex. So a RGBA color attribute would have size 4 and texture 
	 * coordinate or vertex position size 2. The array data must be either a byte 
	 * array or a float array. Currently color attribute accepts only byte arrays
	 * and position and texture coordinate attributes accept only float arrays.
	 * This will propably change in the future to allow a more flexible scheme.
	 */
	static public class Attribute
	{
		private int size;
		private Object data;
		
		/**
		 * Creates an attribute with the given size and array data.
		 * 
		 * @param size the size of the attribute.
		 * @param data the array data.
		 */
		public Attribute(int size, Object data) {
			this.size = size;
			this.data = data;
		}

		/** 
		 * Gets the size of the attribute.
		 */
		public int getSize() {
			return size;
		}

		/**
		 * Gets the data of the attribute.
		 */
		public Object getData() {
			return data;
		}
	}
	
	// Attributes
	/**
	 * The position attribute defines the positions of the vertices.
	 */
	public static final int POSITION_ATTRIBUTE = 0;
	/**
	 * The color attribute defines the diffuse colors of the vertices.
	 */
	public static final int COLOR_ATTRIBUTE = 1;
	/**
	 * The texture coordinate attribute defines the texture coordinates
	 * of the vertices.
	 */
	public static final int TEX_COORD1_ATTRIBUTE = 2;
	private static final int NUM_ATTRIBUTES = 3;
	
	// Fragment parameters
	private static final int NUM_FPS = 0;
	
	// Textures
	private static final int NUM_TEXTURES = 1;
	
	private int type;
	private Attribute[] attributes = new Attribute[NUM_ATTRIBUTES];
	private float[][] fragmentParameters = new float[NUM_FPS][];
	private ScenicImage[] textures = new ScenicImage[NUM_TEXTURES];
	private int[] indices;
	

	Rectangle getBounds(DrawContext context, AffineTransform transform) {
		Attribute positions = attributes[POSITION_ATTRIBUTE];
		
		if(positions == null)
			return new Rectangle();
		Object dataObject = positions.getData();
		int size = positions.getSize();
		double maxPoint[] = new double[2];
		double minPoint[] = new double[2];
		
		if(dataObject instanceof byte[]) {
			byte[] data = (byte[])dataObject;
			
			if(data.length < size)
				return new Rectangle();
			
			for(int j = 0; j < 2; j++)
				maxPoint[j] = minPoint[j] = data[j];
			for(int i = size; i < data.length; i += size) {
				for(int j = 0; j < 2; j++) {
					maxPoint[j] = Math.max(maxPoint[j], data[i + j]);
					minPoint[j] = Math.min(maxPoint[j], data[i + j]);
				}
			}
		}
		else if(dataObject instanceof float[]) {
			float[] data = (float[])dataObject;
			
			if(data.length < size)
				return new Rectangle();
			
			for(int j = 0; j < 2; j++)
				maxPoint[j] = minPoint[j] = data[j];
			for(int i = size; i < data.length; i += size) {
				for(int j = 0; j < 2; j++) {
					maxPoint[j] = Math.max(maxPoint[j], data[i + j]);
					minPoint[j] = Math.min(maxPoint[j], data[i + j]);
				}
			}
		}
		else
			return new Rectangle();
		
		int x = (int)Math.floor(minPoint[0] - 1);
		int y = (int)Math.floor(minPoint[1] - 1);
		int w = (int)Math.floor(maxPoint[0] + 1) - x;
		int h = (int)Math.floor(maxPoint[1] + 1) - y;
		
		return new Rectangle(x, y, w, h);
	}
	
	/**
	 * Gets the given attribute.
	 * 
	 * @param index the index of the attribute.
	 * @return the attribute.
	 */
	public Attribute getAttribute(int index) {
		return attributes[index];
	}
	
	/**
	 * Sets the given attribute. The size defines the number of 
	 * numbers used for a single vertice.
	 * 
	 * @param index the index of the attribute.
	 * @param size the size of the attribute
	 * @param data the data.
	 */
	public void setAttribute(int index, int size, float[] data) {
		attributes[index] = new Attribute(size, data);
		changed();
	}
	
	/**
	 * Sets the given attribute. The size defines the number of 
	 * numbers used for a single vertice.
	 * 
	 * @param index the index of the attribute.
	 * @param size the size of the attribute
	 * @param data the data.
	 */
	public void setAttribute(int index, int size, byte[] data) {
		attributes[index] = new Attribute(size, data);
		changed();
	}
	
	float[] getFragmentParameter(int index) {
		return fragmentParameters[index];
	}
	
	void setFragmentParameter(int index, float[] parameter) {
		fragmentParameters[index] = parameter;
		changed();
	}

	/**
	 * Gets the texture at the given index.
	 * 
	 * @param index the index of the texture.
	 * @return the texture at the given index.
	 */
	public ScenicImage getTexture(int index) {
		return textures[index];
	}

	/**
	 * Sets the texture at the given index. Currently only one texture
	 * is allowed so the index must be 0.
	 * 
	 * @param index the index of the texture.
	 * @param texture the texture.
	 */
	public void setTexture(int index, ScenicImage texture) {
		textures[index] = texture;
		changed();
	}
	
	/**
	 * Gets the indices.
	 * 
	 * @return the indices.
	 */
	public int[] getIndices() {
		return indices;
	}

	/**
	 * Sets the indices.
	 * 
	 * @param indices the indices.
	 */
	public void setIndices(int[] indices) {
		this.indices = indices;
		changed();
	}

	void draw(DrawContext context, AffineTransform transform, Rectangle visibleArea) {
		context.renderer.setTransform(context.context, transform);
		context.renderer.beginPrimitives(context.context);
		for(int i = 0; i < attributes.length; i++) {
			if(attributes[i] == null)
				continue;
			Object data = attributes[i].getData();
			int size = attributes[i].getSize();
			
			if(data instanceof byte[])
				context.renderer.setAttribute(context.context, i, size, (byte[])data);
			else if(data instanceof float[])
				context.renderer.setAttribute(context.context, i, size, (float[])data);
		}
		for(int i = 0; i < fragmentParameters.length; i++) {
			if(fragmentParameters[i] == null)
				continue;
			context.renderer.setFragmentParameter(context.context, i, 
					fragmentParameters[i]);
		}
		for(int i = 0; i < textures.length; i++) {
			if(textures[i] == null)
				continue;		
			context.renderer.setTexture(context.context, i, textures[i].getId()); 
		}
		context.renderer.drawIndices(context.context, type, indices);
		context.renderer.endPrimitives(context.context);
	}
}
